#!/usr/bin/env python2.5

from mock import Mock, MockMethod
import os
import storywiki
from storywiki import Wiki, WikiName, WikiSyntax, Tag, Page, View, FilePersistence, AttributeConverter, PageComparator
import tempfile
import unittest

class WikiTestCase(unittest.TestCase):
    def setUp(self):
        self.emptyWiki = Wiki()
        
    def test_request_for_nonexistent_page_should_return_new_page(self):
        page = self.emptyWiki.get_page('PageName');
        self.assertTrue(page.is_new())
        
    def test_request_for_existing_page_should_return_that_page(self):
        new_page = self.emptyWiki.get_page('PageName');
        existing_page = self.emptyWiki.get_page('PageName');
        self.assertEqual(new_page, existing_page)
        
    def test_request_for_page_with_non_wiki_name_should_be_none(self):
        bad_page = self.emptyWiki.get_page('invalidName')
        self.assertEqual(None, bad_page)

    def test_pages_in_a_wiki_should_track_the_pages_they_refer_to_and_know_their_referring_pages(self):
        page_one = self.emptyWiki.get_page('PageOne')
        page_one.store('A link to PageTwo and another to NameSpaceTwo/PageThree');
        page_two = self.emptyWiki.get_page('PageTwo')
        page_three = self.emptyWiki.get_page('NameSpaceTwo/PageThree')
        self.assertEqual([page_two, page_three], page_one.referred_pages)
        self.assertEqual([page_one], page_two.backlinks())
        self.assertTrue(page_one is page_two.backlinks()[0])
        self.assertEqual([page_one], page_three.backlinks())
        self.assertEqual([], page_two.referred_pages)
        self.assertEqual([], page_one.backlinks())
        page_two.store('A link to PageOne')
        page_one.store('Just a link to PageTwo')
        self.assertEqual([page_two], page_one.referred_pages)
        self.assertEqual([page_one], page_two.backlinks())
        self.assertEqual([], page_three.backlinks())
        self.assertEqual([page_one], page_two.referred_pages)
        self.assertTrue(page_one is page_two.referred_pages[0])
        self.assertEqual([page_two], page_one.backlinks())

class FilePersistenceTestCase(unittest.TestCase):        
    def setUp(self):
        self.tmpdir = tempfile.gettempdir()
        
    def test_default_docbase_should_be_wikibase_below_current_working_directory(self):
        wikibase = os.path.join(os.getcwd(), 'wikibase')
        self.assertEqual(wikibase, FilePersistence().docbase)
        
    def test_persistence_should_use_txt_file_to_store_page(self):
        persistence = FilePersistence(self.tmpdir)
        wiki_name = WikiName('SomePage')
        self.assertEqual(os.path.join(self.tmpdir, 'SomePage.txt'), persistence.get_file_name(wiki_name))
        
    def test_persistence_should_use_subfolders_to_store_page_within_namespace(self):
        persistence = FilePersistence(self.tmpdir)
        wiki_name = WikiName('NameSpace/SomePage')
        path = persistence.docbase
        for part in wiki_name.name_space:
            path = os.path.join(path, part)
        self.assertEqual(os.path.join(path, wiki_name.short_name + '.txt'), persistence.get_file_name(wiki_name))
        
    def test_persistence_should_translate_filename_into_wiki_name_relative_to_docbase(self):
        persistence = FilePersistence()
        wiki_name = WikiName('WikiPage')
        self.assertEqual(wiki_name, 
                         persistence.get_wiki_name(os.path.join(persistence.docbase, wiki_name.short_name + '.txt')))
        wiki_name = WikiName('NameSpace/SubNameSpace/AnotherWikiPage')
        path = persistence.docbase
        for part in wiki_name.name_space:
            path = os.path.join(path, part)
        path = os.path.join(path, wiki_name.short_name + '.txt')
        self.assertEqual(wiki_name, persistence.get_wiki_name(path))
        
    def test_persistence_should_use_wiki_to_load_pages_into(self):
        mock_wiki = Mock(self)
        mock_page = Mock(self)
        wiki_name = WikiName('BlahDeBlah')
        mock_wiki.expect('get_page', wiki_name.full_name).returns(mock_page)
        page_text = 'Some text'
        mock_page.expect('store_text_without_persistence', page_text)
        persistence = FilePersistence()
        persistence.load_page_into_wiki(mock_wiki, wiki_name, page_text)
        mock_wiki.verify()
        mock_page.verify()
    
class WikiNameTestCase(unittest.TestCase):
    def test_string_representation_of_wikiname_should_be_the_name(self):
        self.assertEqual('TheName', str(WikiName('TheName')))
        
    def test_all_uppercase_should_not_be_a_valid_wiki_name(self):
        all_upper_name = WikiName('UPPER')
        self.assertFalse(all_upper_name.is_valid())
        
    def test_all_lowercase_should_not_be_a_valid_wiki_name(self):
        all_lower_name = WikiName('lower')
        self.assertFalse(all_lower_name.is_valid())
        
    def test_mixed_case_starting_with_lower_should_not_be_a_valid_wiki_name(self):
        mixed_name_starting_lower = WikiName('lowerStartToMixedCase')
        self.assertFalse(mixed_name_starting_lower.is_valid())
        
    def test_mixed_case_starting_with_upper_should_be_a_valid_wiki_name(self):
        good_name = WikiName('UpperStartToMixedCase')
        self.assertTrue(good_name.is_valid())
        
    def test_wiki_name_should_be_namespaced_using_folder_notation(self):
        namespaced_name = WikiName('NameSpace/SubNameSpace/WikiName')
        self.assertTrue(namespaced_name.is_valid())
        self.assertEqual('NameSpace/SubNameSpace/WikiName', namespaced_name.full_name)
        self.assertEqual('WikiName', namespaced_name.short_name)
        self.assertEqual(('NameSpace', 'SubNameSpace'), namespaced_name.name_space)
    
    def test_two_wiki_names_should_be_equal_if_their_name_and_namespaces_are_equal(self):
        simple_name = WikiName('SimpleName')
        self.assertEqual(WikiName('SimpleName'), simple_name)
        namespaced_name = WikiName('NameSpace/SimpleName')
        self.assertEqual(WikiName('NameSpace/SimpleName'), namespaced_name)
        self.assertNotEqual(namespaced_name, simple_name)
    
    def test_wiki_name_should_parse_wikinames_from_within_text(self):
        self.assertEqual(['WikiNameOne', 'WikiNameTwo'], 
                         WikiName('SomeName').parse_wiki_names('Text referring to WikiNameOne and WikiNameTwo'))
    
    def test_wiki_name_should_match_attribute_only_if_starts_with_name_part_and_remainder_starts_with_capital(self):
        wiki_name = WikiName('IterationOne')
        self.assertTrue(wiki_name.matches_attribute('Iteration'))
        self.assertFalse(wiki_name.matches_attribute('Iter'))
        self.assertFalse(wiki_name.matches_attribute('IterationOne'))
        wiki_name = WikiName('SubThing/ThingTwo')
        self.assertTrue(wiki_name.matches_attribute('Thing'))

class PageTestCase(unittest.TestCase):
    def setUp(self):
        self.wiki = Wiki()
        self.empty_page = self.wiki.get_page('EmptyPageName')
        self.stored_page_name = 'StoredPageName'
        self.stored_page = self.wiki.get_page(self.stored_page_name)
        self.stored_page.store('some text')
        
    def test_page_should_stringify_with_its_name(self):
        self.assertEqual('EmptyPageName', str(self.empty_page))
        
    def test_empty_page_should_be_new(self):
        self.assertTrue(self.empty_page.is_new())
        
    def test_page_with_text_should_not_be_new(self):
        self.assertFalse(self.stored_page.is_new())
    
    def test_stored_page_should_know_its_name(self):
        self.assertEqual(self.stored_page_name, str(self.stored_page.wiki_name))
    
    def test_storing_changed_text_should_persist_page_and_update_view(self):
        mock_persistence = Mock(self)
        self.wiki.persistence = mock_persistence
        mock_view = Mock(self)
        self.wiki.view = mock_view
        page = self.wiki.get_page('SomeName')
        text_before = 'sidfhsfsdf'
        page.text = text_before
        mock_persistence.expect('persist', page.wiki_name, 'text')
        mock_view.expect('reset_view', page)
        page.store('text')
        mock_persistence.verify()
        mock_view.verify()
        self.assertNotEqual(text_before, page.text)
        
    def test_storing_unchanged_text_should_not_persist_page_nor_update_view(self):
        mock_persistence = Mock(self)
        self.wiki.persistence = mock_persistence
        mock_view = Mock(self)
        self.wiki.view = mock_view
        page = self.wiki.get_page('SomeName')
        text_before = 'same text'
        page.text = text_before
        page.store(text_before)
        mock_persistence.verify()
        mock_view.verify()
        self.assertEqual(text_before, page.text)
        
    def test_page_can_translate_page_references_into_attributes(self):
        self.stored_page.store('Links to AnItem and AnotherItem\r CategoryPage, ValueThree')
        self.assertEqual('Page', self.stored_page.attribute('Category'))
        self.assertEqual(3, self.stored_page.attribute('Value'))
        self.assertEqual(None, self.stored_page.attribute('category'))
        
    def test_page_with_multiple_page_references_should_express_attributes_as_list(self):
        self.stored_page.store('Links to ThingOne and SubThing/ThingTwo.')
        self.assertEqual([1,2], self.stored_page.attribute('Thing'))
        
    def test_page_should_know_if_its_content_uses_backlinks(self):
        self.assertFalse(self.stored_page.text_uses_backlinks())
        self.stored_page.store('A link to AnotherPage')
        self.assertFalse(self.stored_page.text_uses_backlinks())
        self.stored_page.store('@backlinks')
        self.assertTrue(self.stored_page.text_uses_backlinks())
        
    def test_updated_page_should_inform_its_referring_pages(self):
        change_notification = MockMethod(self, 'change_notification')
        change_notification.expect(self.empty_page)
        self.stored_page.change_notification = change_notification
        self.empty_page.store('Something about %s...' % self.stored_page_name)
        change_notification.verify()
    
    def test_updated_page_should_also_inform_pages_previously_referred_to(self):
        self.empty_page.store('Something about %s...' % self.stored_page_name)
        change_notification = MockMethod(self, 'change_notification')
        change_notification.expect(self.empty_page)
        self.stored_page.change_notification = change_notification
        self.empty_page.store('No more links to other pages')
        change_notification.verify()
        
    def test_page_using_backlinks_informed_of_change_should_reset_its_view(self):
        self.stored_page.store('This page uses @backlinks')
        mock_page = Mock(self)
        mock_view = Mock(self)
        mock_view.expect('reset_view', self.stored_page)
        self.wiki.view = mock_view
        self.stored_page.change_notification(mock_page)
        mock_page.verify()
        mock_view.verify()
        
    def test_page_not_using_backlinks_informed_of_change_should_not_reset_its_view(self):
        self.stored_page.store('This page uses no backlinks')
        mock_page = Mock(self)
        mock_view = Mock(self)
        self.wiki.view = mock_view
        self.stored_page.change_notification(mock_page)
        mock_page.verify()
        mock_view.verify()
        
    def test_backlinks_should_be_returned_in_default_sortation(self):
        mock_page_one = Mock(self)
        mock_page_two = Mock(self)
        mock_page_one.expect('__eq__', mock_page_two).returns(False)
        mock_page_one.expect('attribute','Name').returns('PageOne')
        mock_page_two.expect('attribute','Name').returns('PageTwo')
        mock_backlinks = MockMethod(self, 'get_backlinks')
        mock_backlinks.expect(self.stored_page).returns([mock_page_two, mock_page_one])
        self.wiki.get_backlinks = mock_backlinks
        self.assertEqual([mock_page_one, mock_page_two], self.stored_page.backlinks())
        mock_backlinks.verify()
        mock_page_one.verify()
        mock_page_two.verify()
    
class PageComparatorTestCase(unittest.TestCase):
    def setUp(self):
        self.wiki = Wiki()
        self.comparator = PageComparator()
        
    def test_comparator_should_return_zero_if_two_pages_are_equal(self):
        self.assertEqual(0, self.comparator.cmp(self.wiki.get_page('PageOne'), self.wiki.get_page('PageOne')))
        
    def test_comparator_should_return_minusone_if_first_page_name_lt_second(self):
        self.assertEqual(-1, self.comparator.cmp(self.wiki.get_page('PageOne'), self.wiki.get_page('PageTwo')))
        
    def test_comparator_should_return_plusone_if_first_page_name_gt_second(self):
        self.assertEqual(1, self.comparator.cmp(self.wiki.get_page('PageTwo'), self.wiki.get_page('PageOne')))
    
    def test_comparator_should_use_explicit_sortation_if_supplied(self):
        mock_page_one = Mock(self)
        mock_page_two = Mock(self)
        mock_page_one.expect('__eq__', mock_page_two).returns(False)
        mock_page_one.expect('attribute', 'Value').returns(1)
        mock_page_two.expect('attribute', 'Value').returns(2)
        list = [mock_page_two, mock_page_one]
        list.sort(cmp=PageComparator('Value').cmp)
        self.assertEqual([mock_page_one, mock_page_two], list)
        mock_page_one.verify()
        mock_page_two.verify()   
        
class TagTestCase(unittest.TestCase):
    def test_tag_with_content_should_stringify_itself_as_an_xhtml_element(self):
        tag = Tag('element', 'content')
        self.assertEqual('<element>content</element>', str(tag))
        
    def test_tag_with_content_and_attribs_should_stringify_itself_as_an_xhtml_element(self):
        tag = Tag('element', 'content', 'attribA="a"', 'attribB="b"')
        self.assertEqual('<element attribA="a" attribB="b">content</element>', str(tag))
        
    def test_two_tags_are_equal_if_their_stringification_is_equal(self):
        self.assertEqual(Tag('q', 'p'), Tag('q', 'p'))
        self.assertNotEqual(Tag('q', 'p', 'r'), Tag('q', 'p'))
        self.assertEqual(Tag('q', 'p', 'r'), Tag('q', 'p', 'r'))
        
    def test_tag_with_no_content_should_stringify_itself_as_an_empty_element(self):
        tag = Tag('element', '')
        self.assertEqual('<element />', str(tag))
        
    def test_tag_with_nested_tags_should_stringify_as_nested_xhtml_element_with_content(self):
        outer_tag = Tag('outer', 'hmmm')
        inner_tag = Tag('inner', 'some content')
        outer_tag.append(inner_tag)
        self.assertEqual('<outer>hmmm<inner>some content</inner></outer>', str(outer_tag))
        
    def test_tag_should_know_what_has_been_most_recently_nested(self):
        outer_tag = Tag('outer', 'hmmm')
        inner_tag = Tag('inner', 'some content')
        self.assertEqual(inner_tag, outer_tag.append(inner_tag))
        self.assertEqual(inner_tag, outer_tag.most_recently_nested())
        
    def test_tag_should_append_to_most_recently_used_if_tagname_doesnot_match(self):
        outer_tag = Tag('ul', '')
        inner_tag = outer_tag.most_recently_nested('li')
        self.assertEqual(Tag('li', ''), inner_tag)
        self.assertTrue(inner_tag is outer_tag.most_recently_nested('li'))
        
    def test_empty_para_tag_should_not_stringify_itself(self):
        self.assertEqual('<p>Some text</p>', str(Tag('p', 'Some text')))
        self.assertEqual('', str(Tag('p', '')))
        
class WikiSyntaxTestCase(unittest.TestCase):
    def setUp(self):
        self.page = Page(WikiName('WikiSyntaxTestCase'), Wiki())
        self.wiki_syntax = WikiSyntax(self.page)
        
    def test_syntax_tag_tree_should_know_current_tag(self):
        self.assertEqual(None, self.wiki_syntax.current_tag())
        tag = Tag('x', '')
        self.wiki_syntax.append_tag(tag)
        self.assertEqual(tag, self.wiki_syntax.current_tag())
        
    def test_syntax_tag_tree_should_create_tag_if_current_tag_is_not_what_is_requested(self):
        tag = Tag('x', '')
        self.wiki_syntax.append_tag(tag)
        self.assertEqual(Tag('y', ''), self.wiki_syntax.current_tag('y'))
        self.assertEqual(2, len(self.wiki_syntax.html_tags))
        
    def test_syntax_tag_tree_should_not_create_tag_if_current_tag_is_what_is_requested(self):
        tag = Tag('x', '')
        self.wiki_syntax.append_tag(tag)
        self.assertEqual(tag, self.wiki_syntax.current_tag('x'))
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        
    def test_syntax_should_evaluate_at_signs_as_page_attributes(self):
        mock_page = Mock(self)
        mock_page.something = 'something'
        self.wiki_syntax = WikiSyntax(mock_page)
        mock_page.text = '@something is an attribute'
        mock_page.expect('attribute', 'something').returns(None)
        self.assertEqual([mock_page.something], self.wiki_syntax.evaluate_attribute(mock_page, 'something'))
        mock_page.expect('attribute', 'something').returns(None)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<p>something is an attribute</p>', html)
        
    def test_syntax_should_invoke_callable_page_attributes(self):
        mock_page = Mock(self)
        self.wiki_syntax = WikiSyntax(mock_page)
        mock_page.text = '@is_new is a callable attribute'
        mock_page.expect('attribute', 'is_new').returns(None)
        mock_page.expect('is_new').returns(True)
        self.assertEqual([True], self.wiki_syntax.evaluate_attribute(mock_page, 'is_new'))
        mock_page.expect('attribute', 'is_new').returns(None)
        mock_page.expect('is_new').returns(True)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<p>True is a callable attribute</p>', html)
    
    def test_syntax_should_iterate_over_iterable_page_attributes(self):
        mock_page = Mock(self)
        self.wiki_syntax = WikiSyntax(mock_page)
        mock_page.text = 'values of @iterable'
        mock_next = Mock(self)
        mock_next.expect('next').returns(3)
        mock_next.expect('next').returns(4)
        mock_next.expect('next').raises(StopIteration())
        mock_iterable = Mock(self)
        mock_iterable.expect('__iter__').returns(mock_next)
        mock_page.expect('attribute', 'iterable').returns(None)
        mock_page.expect('iterable').returns(mock_iterable)
        self.assertTrue(mock_iterable is self.wiki_syntax.evaluate_attribute(mock_page, 'iterable'))
        mock_page.expect('attribute', 'iterable').returns(None)
        mock_page.expect('iterable').returns(mock_iterable)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_iterable.verify()
        mock_next.verify()
        self.assertEqual('<p>values of 3, 4</p>', html)
        
    def test_syntax_should_display_embedded_attributes_if_referenced(self):
        mock_page = Mock(self)
        mock_page.text = '* @list'
        mock_page.list = [1,2]
        mock_page.expect('attribute', 'list').returns(None)
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<ul><li>1</li><li>2</li></ul>', html)
        
    def test_syntax_should_display_embedded_subattributes_when_referenced_with_variable(self):
        mock_page = Mock(self)
        mock_page.text = '* @list{p:p.lower}'
        mock_page.list = ['ABC','DEF']
        mock_page.expect('attribute', 'list').returns(None)
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<ul><li>abc</li><li>def</li></ul>', html)
    
    def test_syntax_should_sort_embedded_subattributes_when_sortation_is_explicit(self):
        mock_one = Mock(self)
        mock_one.expect('attribute', 'Length').twice().returns(8)
        mock_two = Mock(self)
        mock_two.expect('__eq__', mock_one).returns(False)
        mock_two.expect('attribute', 'Length').twice().returns(5)
        mock_page = Mock(self)
        mock_page.text = '* @list(sort=Length){x:x.Length}'
        mock_page.expect('attribute', 'list').returns([mock_one, mock_two])
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_one.verify()
        mock_two.verify()
        self.assertEqual('<ul><li>5</li><li>8</li></ul>', html) 
        
    def test_syntax_should_test_filter_for_each_page_in_list(self):
        mock_one = Mock(self)
        mock_one.wiki_name = WikiName('BbBbb')
        mock_two = Mock(self)
        mock_two.wiki_name = WikiName('AaAaa')
        mock_two.expect('__str__').returns('AaAaa')
        mock_page = Mock(self)
        mock_page.text = 'values are @list(filter=Aa)'
        mock_page.expect('attribute', 'list').returns([mock_one, mock_two])
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_one.verify()
        mock_two.verify()
        self.assertEqual('<p>values are <a href="/AaAaa">AaAaa</a></p>', html)
    
    def test_syntax_should_cope_with_sort_and_filter_combinations(self):
        mock_one = Mock(self)
        mock_one.wiki_name = WikiName('BbBbb')
        mock_two = Mock(self)
        mock_two.wiki_name = WikiName('AaBbb')
        mock_two.expect('attribute', 'Name').returns('AaBbb')
        mock_two.expect('__str__').returns('AaBbb')
        mock_three = Mock(self)
        mock_three.wiki_name = WikiName('AaAaa')
        mock_three.expect('__eq__', mock_two).returns(False)
        mock_three.expect('attribute', 'Name').returns('AaAaa')
        mock_three.expect('__str__').returns('AaAaa')
        mock_page = Mock(self)
        mock_page.text = 'values are @list(sort=Name,filter=Aa)'
        mock_page.expect('attribute', 'list').returns([mock_two, mock_one, mock_three])
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_one.verify()
        mock_two.verify()
        mock_three.verify()
        self.assertEqual('<p>values are <a href="/AaAaa">AaAaa</a>, <a href="/AaBbb">AaBbb</a></p>', html)
        
    def test_syntax_should_sum_expanded_attributes_when_summation_eplicit(self):
        mock_one = Mock(self)
        mock_one.wiki_name = WikiName('BbBbb')
        mock_one.expect('attribute', 'Value').returns(19)
        mock_two = Mock(self)
        mock_two.wiki_name = WikiName('AaBbb')
        mock_two.expect('attribute', 'Value').returns(23)
        mock_page = Mock(self)
        mock_page.text = 'value is @list(sum=Value)'
        mock_page.expect('attribute', 'list').returns([mock_two, mock_one])
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_one.verify()
        mock_two.verify()
        self.assertEqual('<p>value is 42</p>', html)
    
    def test_syntax_should_gracefully_handle_nonexistent_attribute_references(self):
        mock_page = Mock(self)
        mock_page.text = 'values of @noSuchAttribute'
        mock_page.expect('attribute', 'noSuchAttribute').returns(None)
        mock_page.expect('noSuchAttribute').raises(AttributeError())
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<p>values of None</p>', html)
        
    def test_syntax_should_resolve_page_attributes_expressed_through_wiki_links(self):
        mock_page = Mock(self)
        mock_page.text = '@UserRole'
        mock_page.expect('attribute', 'UserRole').returns('Customer')
        self.wiki_syntax = WikiSyntax(mock_page)
        value = self.wiki_syntax.to_html()
        mock_page.verify()
        self.assertEqual('<p>Customer</p>', value)
        
    def test_syntax_should_turn_wikinames_into_html_links(self):
        self.page.store('TestWikiNameLink')
        self.assertEqual('<p><a href="/TestWikiNameLink">TestWikiNameLink</a></p>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual('p', self.wiki_syntax.html_tags[0].element)
        
    def test_syntax_should_turn_stars_and_hashes_into_li_tags(self):
        self.page.store('*item')
        self.assertEqual('<ul><li>item</li></ul>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual('ul', self.wiki_syntax.html_tags[0].element)
        self.assertEqual(1, len(self.wiki_syntax.html_tags[0].nested_tags))
        self.assertEqual(Tag('li', 'item'), self.wiki_syntax.html_tags[0].nested_tags[0])
        self.setUp()
        self.page.store(' # item')
        self.assertEqual('<ol><li>item</li></ol>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual('ol', self.wiki_syntax.html_tags[0].element)
        self.assertEqual(1, len(self.wiki_syntax.html_tags[0].nested_tags))
        self.assertEqual(Tag('li', 'item'), self.wiki_syntax.html_tags[0].nested_tags[0])
        self.setUp()
        self.page.store(' * item')
        self.assertEqual('<ul><li>item</li></ul>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store('* item')
        self.assertEqual('<ul><li>item</li></ul>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store('* item\n**another item')
        self.assertEqual('<ul><li>item<ul><li>another item</li></ul></li></ul>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store('** item\n**another item')
        self.assertEqual('<ul><li><ul><li>item</li><li>another item</li></ul></li></ul>', self.wiki_syntax.to_html())
    
    def test_syntax_should_expand_tags_within_listelements(self):
        self.page.store("*''a''\r*WikiName")
        self.assertEqual('<ul><li><em>a</em></li><li><a href="/WikiName">WikiName</a></li></ul>', self.wiki_syntax.to_html())
        
    def test_syntax_should_turn_pluses_into_h_tags(self):
        self.page.store('+Heading')
        self.assertEqual('<h2>Heading</h2>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual(Tag('h2', 'Heading'), self.wiki_syntax.html_tags[0])
        self.setUp()
        self.page.store('+ Heading')
        self.assertEqual('<h2>Heading</h2>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store(' + Heading')
        self.assertEqual('<h2>Heading</h2>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store(' ++ Subheading')
        self.assertEqual('<h3>Subheading</h3>', self.wiki_syntax.to_html())
        self.setUp()
        self.page.store(' +++Subsubheading')
        self.assertEqual('<h4>Subsubheading</h4>', self.wiki_syntax.to_html())

    def test_syntax_should_turn_two_singlequotes_into_em_or_strong_tags(self):
        self.page.store("''word''")
        self.assertEqual('<p><em>word</em></p>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual('p', self.wiki_syntax.html_tags[0].element)
        self.setUp()
        self.page.store("'''word'''")
        self.assertEqual('<p><strong>word</strong></p>', self.wiki_syntax.to_html())
        self.assertEqual(1, len(self.wiki_syntax.html_tags))
        self.assertEqual('p', self.wiki_syntax.html_tags[0].element)
        
    def test_syntax_should_turn_bars_into_table_columns(self):
        self.page.store('|a|b|')
        self.assertEqual('<table><tr><td>a</td><td>b</td></tr></table>', self.wiki_syntax.to_html())
    
    def test_syntax_should_expand_attribute_table_columns(self):
        mock_page = Mock(self)
        mock_page.text = '|@links{c:col1=c.val1,col2=c.val2}|'
        mock_one = Mock(self)
        mock_one.expect('attribute', 'val1').returns(1)
        mock_one.expect('attribute', 'val2').returns(2)
        mock_two = Mock(self)
        mock_two.expect('attribute', 'val1').returns(3)
        mock_two.expect('attribute', 'val2').returns(4)
        mock_page.links = [mock_one, mock_two]
        mock_page.expect('attribute', 'links').returns(None)
        self.wiki_syntax = WikiSyntax(mock_page)
        html = self.wiki_syntax.to_html()
        mock_page.verify()
        mock_one.verify()
        mock_two.verify()
        self.assertEqual('<table><tr><td>col1</td><td>col2</td></tr>' \
                         + '<tr><td>1</td><td>2</td></tr>' \
                         + '<tr><td>3</td><td>4</td></tr></table>', 
                         html)
        
    def test_syntax_should_expand_tags_within_bars_in_table_columns(self):
        self.page.store("|''a''|WikiName|")
        self.assertEqual('<table><tr><td><em>a</em></td><td><a href="/WikiName">WikiName</a></td></tr></table>', self.wiki_syntax.to_html())
        
    def test_syntax_should_preserve_word_order_in_non_block_paras(self):
        self.page.store("This is a ''word'' and a WikiLink")
        self.assertEqual('<p>This is a <em>word</em> and a <a href="/WikiLink">WikiLink</a></p>', self.wiki_syntax.to_html())
        
    def test_syntax_should_htmlise_linebreak_wikiname_heading_listitem_emphasis_table_attribute_combos(self):
        self.page.store(r"""+Heading
This is a 
NameSpaced/TestWikiNameLink ''and'' a
 ++Subheading
3+4=7
 * 4+3=7
 ** 4+4=8
* 5+2=7
  **2+2=4
Pay attention to '''something''' important.
++ Another Subheading
 # item 1
 ## subitem
 ## another subitem
 # item 2
 |a|b|c|
 |1|2|3|
  * unordered 1
 # ordered 1
Backlinks: @backlinks""")
        self.assertEqual('' \
             + '<h2>Heading</h2><p>This is a </p>' \
             + '<p><a href="/NameSpaced/TestWikiNameLink">NameSpaced/TestWikiNameLink</a> <em>and</em> a</p>' \
             + '<h3>Subheading</h3><p>3+4=7</p>' \
             + '<ul><li>4+3=7<ul><li>4+4=8</li></ul></li><li>5+2=7<ul><li>2+2=4</li></ul></li></ul>' \
             + '<p>Pay attention to <strong>something</strong> important.</p>' \
             + '<h3>Another Subheading</h3>' \
             + '<ol><li>item 1<ol><li>subitem</li><li>another subitem</li></ol></li><li>item 2</li></ol>' \
             + '<table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table>' \
             + '<ul><li>unordered 1</li></ul><ol><li>ordered 1</li></ol>' \
             + '<p>Backlinks: </p>',
                 self.wiki_syntax.to_html())
        
class ViewTestCase(unittest.TestCase):
    def setUp(self):
        self.wiki = Wiki()
        self.view = self.wiki.view
        
    def test_editable_view_should_recognise_post_from_cancel_button(self):
        self.assertTrue(self.view.is_post_from_cancel_button({View.cancel_button:'Cancel'}))
        self.assertFalse(self.view.is_post_from_cancel_button({View.ok_button:'OK'}))
        self.assertFalse(self.view.is_post_from_cancel_button({View.edit_button:'Edit'}))

    def test_editable_view_should_recognise_post_from_ok_button(self):
        self.assertFalse(self.view.is_post_from_ok_button({View.cancel_button:'Cancel'}))
        self.assertTrue(self.view.is_post_from_ok_button({View.ok_button:'OK'}))
        self.assertFalse(self.view.is_post_from_ok_button({View.edit_button:'Edit'}))
        
    def test_editable_view_should_extract_page_text_from_post_args(self):
        some_text = 'This is some text'
        self.assertEqual(some_text, self.view.get_page_text({View.page_text_area: some_text}))
        
    def test_view_should_recognise_post_from_edit_button(self):
        self.assertFalse(self.view.is_post_from_edit_button({View.cancel_button:'Cancel'}))
        self.assertFalse(self.view.is_post_from_edit_button({View.ok_button:'OK'}))
        self.assertTrue(self.view.is_post_from_edit_button({View.edit_button:'Edit'}))
        
    def test_view_should_render_editable_when_edit_is_posted(self):
        mock_page = Mock(self)
        self.assertTrue(self.view.update_from_post(mock_page, {'edit':'Edit'}))
        mock_page.verify()
        
    def test_view_should_update_page_from_posted_arguments_and_render_noneditable(self):
        mock_page = Mock(self)
        mock_page.expect('store', 'updated text')
        self.assertFalse(self.view.update_from_post(mock_page, {'page_text':'updated text', 'ok':'OK'}))
        mock_page.verify()
        
    def test_view_should_update_page_with_html_decoded_text_and_render_noneditable(self):
        mock_page = Mock(self)
        mock_page.expect('store', 'this ~text is html encoded')
        self.assertFalse(self.view.update_from_post(mock_page, 
                        {'page_text':'this+%7etext+is+html%20encoded', 'ok':'OK'}))
        mock_page.verify()
        
    def test_view_should_render_noneditable_when_update_is_cancelled(self):
        mock_page = Mock(self)
        self.assertFalse(self.view.update_from_post(mock_page, {'page_text':'updated text', 'cancel':'Cancel'}))
        mock_page.verify()
        
    def test_rendering_same_page_twice_should_only_invoke_view_once(self):
        page = Page(WikiName('SomeName'), self.wiki)
        args = {}
        mock_renderer = MockMethod(self, 'render_editable')
        mock_renderer.expect(page).returns('some html')
        self.view.render_editable = mock_renderer
        view_before = self.view.render(page, args)
        view_after = self.view.render(page, args)
        mock_renderer.verify()
        self.assertEqual(view_before, view_after)
        
    def test_changing_page_editability_should_change_rendered_view(self):
        page = Page(WikiName('SomePage'), self.wiki)
        page.store('some text')
        mock_noneditable = MockMethod(self, 'render_noneditable')
        mock_noneditable.expect(page).returns('some html')
        self.view.render_noneditable = mock_noneditable
        mock_editable = MockMethod(self, 'render_editable')
        mock_editable.expect(page).returns('some other html')
        self.view.render_editable = mock_editable
        view_before = self.view.render(page, {})
        view_after = self.view.render(page, {View.edit_button:'Edit'})
        mock_noneditable.verify()
        mock_editable.verify()
        self.view.render_editable.verify()
        self.assertNotEqual(view_before, view_after)
    
    def test_empty_page_should_always_be_rendered_as_editable(self):
        page = Page(WikiName('SomeEmptyPage'), self.wiki)
        mock_renderer = MockMethod(self, 'render_editable')
        self.view.render_editable = mock_renderer
        mock_renderer.expect(page).returns('some html')
        html = self.view.render(page, {})
        mock_renderer.verify()
        self.assertEqual('some html', html)
        page = Page(WikiName('PageWithSomeContent'), self.wiki)
        page.text = 'haskdhaskjh' 
        mock_renderer.expect(page).returns('some other html')
        html = self.view.render(page, {'page_text':'', 'ok':'OK'}) # simulate an update to empty text
        mock_renderer.verify()
        
    def test_stored_page_should_be_rendered_by_default_as_noneditable(self):
        page = Page(WikiName('SomePageWithContent'), self.wiki)
        page.store('some text')
        mock_renderer = MockMethod(self, 'render_noneditable')
        mock_renderer.expect(page).returns('some html')
        self.view.render_noneditable = mock_renderer
        html = self.view.render(page, {})
        mock_renderer.verify()
        self.assertEqual('some html', html)
        
    def test_resetting_view_for_page_should_cause_rerendering(self):
        page = Page(WikiName('SomePageWithContent'), self.wiki)
        page.store('some text')
        mock_renderer = MockMethod(self, 'render_noneditable')
        mock_renderer.expect(page).twice().returns('some html')
        self.view.render_noneditable = mock_renderer
        self.view.render(page, {})
        self.view.reset_view(page)
        self.view.render(page, {})
        mock_renderer.verify()
        
class AttributeConverterTest(unittest.TestCase):
    def test_conversion_handles_single_digit_numbers(self):
        converter = AttributeConverter()
        strings = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
        expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
            
    def test_conversion_handles_double_digit_numbers_less_than_twenty(self):
        converter = AttributeConverter()
        strings = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 
                   'sixteen', 'seventeen', 'eighteen', 'nineteen']
        expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
            
    def test_conversion_handles_double_digit_numbers_more_than_twenty(self):
        converter = AttributeConverter()
        strings = ['twenty', 'twentyone', 'thirtytwo', 'fortythree', 'fifty', 'sixtyfour', 
                   'seventyfive', 'eightysix', 'ninetyseven', 'fiftyeight', 'twentynine']
        expected = [20, 21, 32, 43, 50, 64, 75, 86, 97, 58, 29].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
        
    def test_conversion_handles_three_digit_numbers(self):
        converter = AttributeConverter()
        strings = ['onehundred', 'twohundredandone', 'threehundredandtwenty', 'fourhundredandfiftysix']
        expected = [100, 201, 320, 456].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
            
    def test_conversion_handles_four_digit_numbers(self):
        converter = AttributeConverter()
        strings = ['onethousand', 'twothousandandone', 'threethousandandtwentyfour', 
                   'fivethousandsixhundredandseventyeight']
        expected = [1000, 2001, 3024, 5678].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
    
    def test_conversion_handles_five_and_six_digit_numbers(self):
        converter = AttributeConverter()
        strings = ['twelvethousandthreehundredandfortyfive', 
                   'sixhundredandseventyeightthousandninehundred']
        expected = [12345, 678900].__iter__()
        for string in strings:
            self.assertEqual(expected.next(), converter.convert_to_number(string))
    
    def test_conversion_handles_unconvertable_values(self):
        converter = AttributeConverter()
        self.assertEqual(None, converter.convert_to_number(None))
        self.assertEqual('xyz', converter.convert_to_number('xyz'))
        self.assertEqual('ABC', converter.convert_to_number('ABC'))
        
    def test_conversion_handles_mixedcase_values(self):
        converter = AttributeConverter()
        self.assertEqual(1, converter.convert_to_number('One'))
        self.assertEqual(23, converter.convert_to_number('TwentyThree'))
        self.assertEqual(321, converter.convert_to_number('ThreeHundredAndTwentyOne'))
        
    def test_conversion_expresses_nonnumeric_attributes_as_separate_words(self):
        converter = AttributeConverter()
        self.assertEqual('In progress', converter.convert('StatusInProgress', 'Status'))
            
if __name__ == '__main__':
    reload(storywiki)
    try: unittest.main()
    except SystemExit: pass